using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using System.Collections;
using System.Reflection;
using System.Security.Permissions;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Data;
using System.Data.Odbc;
using NumericalRecipes;
using FftLib;
using BmgNet;
using StaticUtility;

namespace ObjectLib
{
    /// <summary>
    /// [Serializable]
    /// Data for a single object (image segment) within an ObjectParser project.  
    /// </summary>
    [Serializable]
    public class ParsedObject: ISerializable,IDeserializationCallback, IXmlSerializable
    {
        /// <summary>
        /// Name of object.
        /// </summary>
        public string Name;
        /// <summary>
        /// The address of this object in the TreeView.
        /// </summary>
        public string Address
        {
            get
            {
                if(this.Category!=null)
                    return this.Category.Path + '/' + this.Name;
                return this.Name;
            }
        }
        /// <summary>
        /// Supports searching for objects.
        /// </summary>
        public string SearchText;
        /// <summary>
        /// Gets the active contour associated with this ParsedObject.
        /// </summary>
        public ContourF ActiveContour
        {
            get
            {
                _pfTemp = this.ActivePoly;
                if(_pfTemp==null)
                    return null;
                return _pfTemp.ActiveContour;
            }
        }
        /// <summary>
        /// The index of the active polygon.
        /// </summary>
        public int ActivePolyInd;
        /// <summary>
        /// The Polygons that define object.
        /// </summary>
        public PolygonFarray Polygons;
        /// <summary>
        /// The object's category.
        /// </summary>
        public CategoryNode Category;
        /// <summary>
        /// The categorization data for the object.
        /// </summary>
        public ObjectCategorizationData CatData;
        /// <summary>
        /// Contains a list of occlusion bridges that link different polygons in the ParsedObject.
        /// </summary>
        public OcclusionBridgeList Bridges;
        public void IncrementObjectInventory(int[][] output)
        {
            this.CatData.IncrementInventory(output);
        }
        public void IncrementContourInventory(int[][] output)
        {
            PolygonF pf;
            ContourF con;
            int i,j;
            for(i=0;i<this.Polygons.Count;i++)
            {
                pf = this.Polygons[i];
                for(j=0;j<pf.Contours.Count;j++)
                {
                    con = pf.Contours[j];
                    if(con.Count>1)
                        con.CatData.IncrementInventory(output);
                }
            }
        }
        /// <summary>
        /// Gets text for the tooltip in the TreeView (Tv) of ObjectParser.  Includes
        /// information from categorization (if available).
        /// </summary>
        public string TooltipText
        {
            get
            {
                string conStr = "No contours\n";
                //	Contour categorization
                int ct = 0,i;
                for(i=0;i<this.Polygons.Count;i++)
                    ct+= this.Polygons[i].Contours.Count;
                if(ct >1)
                    conStr = ct.ToString() + " contours\n";
                else if(ct==1)
                    conStr = "1 contour\n";
                if(ct>0)
                {
                    int[][] inventory = ContourCategorizationData.EmptyInventory;
                    this.IncrementContourInventory(inventory);
                    conStr = conStr + ContourCategorizationData.InventoryTooltipText(inventory);
                }
                //	Parsed object categorization
                if(CatData!=null)
                    conStr = CatData.TooltipText + '\n' + conStr + '\n';
                else
                    conStr = "No categorization." + '\n'  + conStr + '\n';
                if(Polygons.Count==1)
                    return Name + ":  " + Convert.ToString(this.Polygons.Count) + " polygon\n" + conStr;
                else
                    return Name + ":  " + Convert.ToString(this.Polygons.Count) + " polygons\n" + conStr;
            }
        }
        /// <summary>
        /// Empty constructor.
        /// </summary>
        public ParsedObject() { }
        /// <summary>
        /// Constructs an object with the specified Id number.
        /// </summary>
        /// <param name="objectId"></param>
        public ParsedObject(int objectId,CategoryNode cat)
        {
            ActivePolyInd = 0;
            Polygons = new PolygonFarray();
            Name = "obj_" + Convert.ToString(objectId);
            Category = cat;
            SearchText = "";
            CatData = new ObjectCategorizationData();
            Bridges = new OcclusionBridgeList();
        }
        #region Serialization
        public ParsedObject(SerializationInfo info,StreamingContext context)
        {	//	info.Get____
            Name = info.GetString("Name");

            try { SearchText = info.GetString("SearchText"); }
            catch { SearchText = ""; }

            try { CatData = (ObjectCategorizationData)info.GetValue("CatData",typeof(ObjectCategorizationData)); }
            catch { CatData = new ObjectCategorizationData(); }

            try { DbCache_UniqueId = info.GetUInt32("DbCache_UniqueId"); }
            catch { DbCache_UniqueId = 0u; }

            try { DbCache_MaskFileName = info.GetString("DbCache_MaskFileName"); }
            catch { DbCache_MaskFileName = ""; }

            ActivePolyInd = info.GetInt32("ActivePolyInd");

            //	Backwards compatible with PolygonFlist
            try { Polygons = (PolygonFarray)info.GetValue("Polygons",typeof(PolygonFarray)); }
            catch
            {
                pflistOnDeserialization =
					(PolygonFlist)info.GetValue("Polygons",typeof(PolygonFlist));
            }

            try { Bridges = (OcclusionBridgeList)info.GetValue("Bridges",typeof(OcclusionBridgeList)); }
            catch { Bridges = new OcclusionBridgeList(); }
        }
        public virtual void GetObjectData(SerializationInfo info,StreamingContext context)
        {
            info.AddValue("Name",Name);
            info.AddValue("SearchText",SearchText);
            info.AddValue("ActivePolyInd",ActivePolyInd);
            info.AddValue("Polygons",Polygons);
            info.AddValue("CatData",CatData);
            info.AddValue("DbCache_UniqueId",DbCache_UniqueId);
            info.AddValue("DbCache_MaskFileName",DbCache_MaskFileName);

            //  Note, some of the old data files have bad occlusion bridges, so this fix is here to insure the old bridges don't get saved.
            for(int i=0;i<Bridges.Count;i++)
                if(    Bridges[i].Pf1==null || Bridges[i].Pf2==null
                    || Bridges[i].Vf1==null || Bridges[i].Vf2==null
                    || Bridges[i].Pf1.Owner==null || Bridges[i].Pf2.Owner==null)
                    Bridges.RemoveAt(i--);

            info.AddValue("Bridges",Bridges);
        }
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            throw new Exception("The method or operation is not implemented.");
        }
        public void ReadXml(System.Xml.XmlReader reader)
        {
            bool reading=false;
            int depth = reader.Depth;
            while((reading || reader.Read()) && reader.Depth>depth)
            {
                reading = false;
                if((reader.NodeType == System.Xml.XmlNodeType.Element || reader.NodeType==System.Xml.XmlNodeType.Text) && reader.Name!="")
                {
                    reading = true;
                    switch(reader.Name)
                    {
                        case "Name":
                            {
                                this.Name = reader.ReadElementContentAsString();
                            } break;
                        case "ActivePolyInd":
                            {
                                this.ActivePolyInd = reader.ReadElementContentAsInt();
                            } break;
                        case "Bridges":
                            {
                                this.Bridges = new OcclusionBridgeList();
                                reading = false;
                            } break;
                        case "OcclusionBridge":
                            {
                                OcclusionBridge br = new OcclusionBridge(null,null,null,null);
                                this.Bridges.Add(br);
                                br.ReadXml(reader);
                            } break;
                        case "ObjectCategorizationData":
                            {
                                this.CatData = new ObjectCategorizationData();
                                this.CatData.ReadXml(reader);
                            } break;
                        case "DbCache_MaskFileName":
                            {
                                this.DbCache_MaskFileName = reader.ReadElementContentAsString();
                            } break;
                        case "DbCache_UniqueId":
                            {
                                this.DbCache_UniqueId = (uint)reader.ReadElementContentAsLong();
                            } break;
                        case "PolygonsById":
                            {
                                this.Polygons = new PolygonFarray(false);
                                reading = false;
                            } break;
                        case "PolyId":
                            {
                                PolygonF poly = new PolygonF();
                                this.Polygons.Add(poly);
                                poly.SerializationPointer = reader.ReadElementContentAsInt();
                            } break;
                        case "SearchText":
                            {
                                this.SearchText = reader.ReadElementContentAsString();
                            } break;
                        default: throw new Exception("Unexpected field encounterd while reading XML for a ParsedObject object.  Field name = " + reader.Name);
                    }
                }
            }
            if(this.Polygons!=null)
            {
                this.Polygons.AutoSort = true;
                this.Polygons.Sort();
            }
        }
        public void WriteXml(System.Xml.XmlWriter writer)
        {
            int i;

            writer.WriteStartElement("ParsedObject");

            writer.WriteElementString("Name", this.Name);

            writer.WriteElementString("ActivePolyInd", this.ActivePolyInd.ToString());

            writer.WriteStartElement("Bridges");
            for (i = 0; i < Bridges.Count; i++)
                Bridges[i].WriteXml(writer);
            writer.WriteEndElement();

            CatData.WriteXml(writer);

            writer.WriteElementString("DbCache_MaskFileName", this.DbCache_MaskFileName);

            writer.WriteElementString("DbCache_UniqueId", this.DbCache_UniqueId.ToString());

            writer.WriteStartElement("PolygonsById");
            for (i = 0; i < Polygons.Count; i++)
                writer.WriteElementString("PolyId", Polygons[i].SerializationPointer.ToString());
            writer.WriteEndElement();

            writer.WriteElementString("SearchText",this.SearchText);

            writer.WriteEndElement();
        }
        [NonSerialized]
        private PolygonFlist pflistOnDeserialization = null;
        public void OnDeserialization(object sender)
        {
            if(pflistOnDeserialization != null)
                Polygons = new PolygonFarray(pflistOnDeserialization);
            pflistOnDeserialization=null;
            for(int i=0;i<Polygons.Count;i++)
                Polygons[i].Owner = this;
        }
        #endregion
        /// <summary>
        /// True if there are no polygons.
        /// </summary>
        public bool Empty
        {
            get
            {
                return (Polygons.Count == 0);
            }
        }
        /// <summary>
        /// Gets the previous poly in line to be activated.
        /// </summary>
        public PolygonF PreviousPoly
        {
            get
            {
                if(Polygons!=null && Polygons.Count>0)
                {
                    int i=ActivePolyInd-1;
                    if(i<0)
                        i=Polygons.Count-1;
                    return Polygons[i];
                }
                return null;
            }
        }
        /// <summary>
        /// Gets the next poly in line to be activated.
        /// </summary>
        public PolygonF NextPoly
        {
            get
            {
                if(Polygons!=null  && Polygons.Count>0)
                {
                    int i=ActivePolyInd+1;
                    if(i>=Polygons.Count)
                        i=0;
                    return Polygons[i];
                }
                return null;
            }
        }
        /// <summary>
        /// Gets the rectangle that surrounds the set of points.
        /// </summary>
        /// <param name="pts"></param>
        /// <returns></returns>
        public static Rectangle GetBoundingRectPts(ArrayList pts)
        {
            if(pts==null) return new Rectangle(0,0,0,0);

            int l,r,t,b;
            l=r=((Point)pts[0]).X;
            t=b=((Point)pts[0]).Y;

            for(int i=1;i<pts.Count;i++)
            {
                if(((Point)pts[i]).X<l) l=((Point)pts[i]).X;
                if(((Point)pts[i]).X>r) r=((Point)pts[i]).X;
                if(((Point)pts[i]).Y<t) t=((Point)pts[i]).Y;
                if(((Point)pts[i]).Y>b) b=((Point)pts[i]).Y;
            }
            return new Rectangle(l,t,r-l+1,b-t+1);
        }
        /// <summary>
        /// If the pixel is part of the object, returns the index of the polygon containing the pixel.  Otherwise, returns -1.
        /// This relies on PolygonF.Pix being updated before the call.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public int PixelInObject(int x,int y)
        {
            for(int i=0;i<Polygons.Count;i++)
                if(Polygons[i].Pix.PixelIsTagged(x,y))
                    return i;
            return -1;
        }
        /// <summary>
        /// If the pixel is part of the object, returns the index of the polygon containing the pixel.  Otherwise, returns -1.
        /// This does not rely on the PixelTagger caches.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public int PixelInObject(float x,float y)
        {
            for(int i=0;i<Polygons.Count;i++)
                if(Polygons[i].InPoly(x,y)>0)
                    return i;
            return -1;
        }
        /// <summary>
        /// Creates a new polygon, adds it to the member variable Polygons, then returns the created polygon.
        /// If there exists an element of Polygons that is empty, a new polygon is not created and 
        /// the pre-existing empty polygon is returned.
        /// </summary>
        /// <returns></returns>
        public PolygonF CreateNewPoly()
        {
            for(int i=0;i<Polygons.Count;i++)
                if(((PolygonF)Polygons[i]).Count == 0)
                    return (PolygonF)Polygons[i];
            PolygonF pf = new PolygonF(this);
            Polygons.Add(pf);
            return pf;
        }
        /// <summary>
        /// For any members of Polygons, execute Polygons[.].RemoveSwallowedBy().
        /// </summary>
        public void RemoveSwallowedBy()
        {
            for(int i=0;i<Polygons.Count;i++)
                Polygons[i].RemoveSwallowedBy();
        }
        /// <summary>
        /// For any members of Polygons, execute Polygons[.].RestoreSwallowedBy().
        /// </summary>
        public void RestoreSwallowedBy()
        {
            for(int i=0;i<Polygons.Count;i++)
                Polygons[i].RestoreSwallowedBy();
        }
        /// <summary>
        /// Insures that VertexF children of Polygons have the deleted polygons removed from their list of Owners.
        /// </summary>
        public void RemoveVertexF()
        {
            for(int i=0;i<this.Polygons.Count;i++)
                ((PolygonF)Polygons[i]).RemoveVertexF();
        }
        /// <summary>
        /// Does the opposite of RemoveVertexF.
        /// </summary>
        public void ReclaimVertices()
        {
            for(int i=0;i<Polygons.Count;i++)
                Polygons[i].ReclaimVertices();
        }
        public EdgeGroupArray AfterChange_ValidateEdges()
        {
            //	Keep track of busted edges after deleting object
            EdgeGroupArray totalGroups=new EdgeGroupArray(),thisGroup;
            int i,j;
            for(i=0;i<this.Polygons.Count;i++)
            {
                thisGroup = this.Polygons[i].ValidateEdges();
                for(j=0;j<thisGroup.Count;j++)
                    totalGroups.Add(thisGroup[j]);
            }
            return totalGroups;
        }
        /// <summary>
        /// Appends the group of polygons in this category containing the point onto the PolygonFlist provided by the user.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="output">PolygonFlist  The output is appended to this list.</param>
        /// <returns></returns>
        public void PolygonsContainingPoint(float x,float y,PolygonFlist output)
        {
            PolygonF activePoly = ActivePoly;

            for(int i=0;i<this.Polygons.Count;i++)
                if(Polygons[i].Pix.PixelIsTagged((int)(x+0.5f),(int)(y+0.5f)))
                    output.Add(Polygons[i]);
        }
        public void PolygonsInRegion(PolygonF region,PolygonFlist output)
        {
            for(int i=0;i<Polygons.Count;i++)
            {
                if(PolygonF.Overlaps(region,Polygons[i]))
                    if(!output.Contains(Polygons[i]))
                        output.Add(Polygons[i]);
            }
        }
        public static float FieldValueExtract(string fieldResidue)
        {
            fieldResidue.Replace(";","");
            fieldResidue.Replace(" ","");
            fieldResidue.Replace("\n","");
            fieldResidue.Replace("t","");
            return (float)Convert.ToDecimal(fieldResidue);
        }
        public float FieldValue(string text)
        {
            return 0.0f;
        }
        public static float Contains(string container,string contained)
        {
            if(container.Length < contained.Length)
                return 0.0f;
            int diffLen = container.Length-contained.Length;
            bool found;
            for(int i=0;i<diffLen;i++)
            {
                found = true;
                for(int j=0;(j<contained.Length) && found;j++)
                    found = container[i+j]==contained[j];
                if(found)
                    return 1.0f;
            }
            return 0.0f;
        }
        public float SearchTextValue(string text)
        {
            if(SearchText == null)
                return 0.0f;
            for(int i=0;i<SearchText.Length;i++)
            {
                if(1.0f==Contains(SearchText,text))
                    return 1.0f;
            }
            return 0.0f;
        }
        public void ReducePix()
        {
            for(int i=0;i<Polygons.Count;i++)
                Polygons[i].ReducePix();
        }
        public Rectangle BoundingRect
        {
            get
            {
                int ct = 0;
                int left=0,right=0,top=0,bottom=0;
                for(int i=0;i<Polygons.Count;i++)
                    if(!Polygons[i].Empty)
                    {
                        ct++;
                        Rectangle rect = Polygons[i].GetBoundingRect(0.0f);
                        if(ct==1)
                        {
                            left = rect.Left;
                            right= rect.Right;
                            top  = rect.Top;
                            bottom = rect.Bottom;
                        }
                        else
                        {
                            left = Math.Min(left,rect.Left);
                            right = Math.Max(right,rect.Right);
                            top = Math.Min(top,rect.Top);
                            bottom = Math.Max(bottom,rect.Bottom);
                        }
                    }

                if(ct==0)
                    return new Rectangle(0,0,0,0);
                else
                    return new Rectangle(left,top,right-left+1,bottom-top+1);
            }
        }
        /// <summary>
        /// Gets the active polygon.
        /// </summary>
        public PolygonF ActivePoly
        {
            get
            {
                if(Polygons.Count == 0) return null;
                if(ActivePolyInd < 0)
                    ActivePolyInd = 0;
                else if(ActivePolyInd>=Polygons.Count)
                    ActivePolyInd = Polygons.Count-1;
                return (PolygonF)Polygons[ActivePolyInd];
            }
            set
            {
                ActivePolyInd = Polygons.IndexOf(value);
            }
        }
        public int NumberOfVertices
        {
            get
            {
                int output = 0;
                for(int i=0;i<Polygons.Count;i++)
                    output += Polygons[i].Count;
                return output;
            }
        }
        /// <summary>
        /// If you desire nPix pixels from this ParsedObject, then the number of points that you should
        /// draw from each polygon is returned.
        /// </summary>
        /// <param name="nPix">The number of pixels desired from this object.</param>
        /// <returns>The number of pixels you should draw from each polygon in this.Polygons.</returns>
        public int[] NpixPerPoly(int nPix)
        {
            if(this.Polygons.Count==0 || this.Polygons.Count==1 && this.Polygons[0].Pix.NumPts<=1)
                return null;
            //	The area of each polygon
            float[] area = new float[this.Polygons.Count];
            float totalArea = 0.0f;
            for(int i=0;i<Polygons.Count;i++)
            {
                area[i] = (float)Polygons[i].Pix.NumPts;
                totalArea += area[i];
            }
            for(int i=0;i<Polygons.Count;i++)		//	Normalize the area by the total area of the object.
                area[i] /= totalArea;

            //	Determine the number of pixels to select from each polygon.
            int[] nPixPerPoly = new int[this.Polygons.Count];
            int pixLeft = nPix;
            for(int i=0;i<Polygons.Count;i++)
            {
                nPixPerPoly[i] = (int)((float)nPix*area[i]+0.5f);
                pixLeft -= nPixPerPoly[i];
            }
            int ct = 0;
            while(pixLeft > 0)		//	If overshot (selected too many pixels).
            {
                nPixPerPoly[ct++]++;
                pixLeft--;
                if(ct >= this.Polygons.Count)
                    ct=0;
            }
            while(pixLeft < 0)	//	If undershot (didn't select enough pixels).
            {
                nPixPerPoly[ct++]--;
                pixLeft++;
                if(ct >= this.Polygons.Count)
                    ct = 0;
            }
            return nPixPerPoly;
        }
        public Point[] RandomPix(int nPix)
        {
            int[] pixPerPoly = NpixPerPoly(nPix);
            if(pixPerPoly==null)
                return null;
            int nThis;
            Point[] output = new Point[nPix];
            int ct=0;
            int i,j;
            for(i=0;i<pixPerPoly.Length;i++)
            {
                nThis= pixPerPoly[i];
                for(j=0;j<nThis;j++)
                    output[ct++] = Polygons[i].Pix.RandomPointInPix;
            }
            return output;
        }
        /// <summary>
        /// Computes the mean of each pixel in the Log-Lms-based opponent space by relying on nPix random pixels
        /// from this ParsedObject.  The space consists of 5 elements:  Lum, Yb, Rg, Hue, Sat.
        /// </summary>
        /// <param name="nPix">The number of pixels to base this calculation on.</param>
        /// <param name="mean">Returns a 5-element mean.  The color-space
        /// consists of 5 ordered elements:  Lum, Yb, Rg, Hue, Sat.</param>
        /// <param name="cov">Returns a 5 x 5 covariance matrix.  The color-space
        /// consists of 5 ordered elements:  Lum, Yb, Rg, Hue, Sat.</param>
        /// <returns>The number of pixels actually evaluated.  Pixels are skipped when one of the pixelValues is 4095 (maxed out)
        /// or when it is outside the scope of the indices of pixelValues (outside the actual image).</returns>
        public int LmsOppStats(int nPix,out double[] mean,out double[,] cov,Ushort3 pixelValues,float rgbFac)
        {
            mean = new double[5];
            cov = new double[5,5];

            Point[] pts = RandomPix(nPix);
            if(pts!=null)
            {
                Point pt;

                float[] rgb = new float[3];
                float[] lms = new float[3];
                float[] lyr = new float[3];
                double chroma;

                int i,ct=0;
                //	Compute the mean values
                for(i=0;i<nPix;i++)
                {
                    pt = pts[i];
                    if(pt.X < pixelValues.Nj && pt.Y < pixelValues.Ni
						&&	pixelValues[pt.Y,pt.X,0]<4095
						&&	pixelValues[pt.Y,pt.X,1]<4095
						&&	pixelValues[pt.Y,pt.X,2]<4095)
                    {
                        ct++;
                        rgb[0] = (float)pixelValues[pt.Y,pt.X,0]*rgbFac;
                        rgb[1] = (float)pixelValues[pt.Y,pt.X,1]*rgbFac;
                        rgb[2] = (float)pixelValues[pt.Y,pt.X,2]*rgbFac;
                        KodakColorSpace.RgbToLogLms(rgb,lms);
                        KodakColorSpace.LogLmsToLumYbRg(lms,lyr);
                        chroma = (double)KodakColorSpace.LumYbRgToChroma(lyr);
                        mean[0] += (double)lyr[0];
                        mean[1] += (double)lyr[1];
                        mean[2] += (double)lyr[2];
                        mean[4] += chroma;
                    }
                    else
                        pts[i] = Point.Empty;
                }
                if(ct>1)
                {
                    double ctd = (double)ct;
                    double lum,yb,rg,hue;
                    //	Divide
                    mean[0]/=ctd;
                    mean[1]/=ctd;
                    mean[2]/=ctd;
                    mean[4]/=ctd;
                    //	The mean of hue is special...it is the angle of the means of yb and rg.
                    mean[3] = Math.Atan2(mean[1],mean[2]);

                    //	Compute the covariance values
                    for(i=0;i<nPix;i++)
                    {
                        pt = pts[i];
                        if(pt!=PointF.Empty)
                        {
                            rgb[0] = (float)pixelValues[pt.Y,pt.X,0]*rgbFac;
                            rgb[1] = (float)pixelValues[pt.Y,pt.X,1]*rgbFac;
                            rgb[2] = (float)pixelValues[pt.Y,pt.X,2]*rgbFac;
                            KodakColorSpace.RgbToLogLms(rgb,lms);
                            KodakColorSpace.LogLmsToLumYbRg(lms,lyr);
                            lum = lyr[0]-mean[0];
                            yb = lyr[1]-mean[1];
                            rg = lyr[2]-mean[2];
                            hue = Math.Atan2(lyr[1],lyr[2])-mean[3];
                            if(hue>Util.TwoPi)
                                hue-=Util.TwoPi;
                            else if(hue<-Util.TwoPi)
                                hue+=Util.TwoPi;
                            if(hue>Math.PI)
                                hue = -(Util.TwoPi-hue);
                            else if(hue<-Math.PI)
                                hue = Util.TwoPi+hue;
                            chroma = (double)KodakColorSpace.LumYbRgToChroma(lyr)-mean[4];

                            cov[0,0] += lum*lum;
                            cov[0,1] += lum*yb;
                            cov[0,2] += lum*rg;
                            cov[0,3] += lum*hue;
                            cov[0,4] += lum*chroma;

                            cov[1,1] += yb*yb;
                            cov[1,2] += yb*rg;
                            cov[1,3] += yb*hue;
                            cov[1,4] += yb*chroma;

                            cov[2,2] += rg*rg;
                            cov[2,3] += rg*hue;
                            cov[2,4] += rg*chroma;

                            cov[3,3] += hue*hue;
                            cov[3,4] += hue*chroma;

                            cov[4,4] += chroma*chroma;
                        }
                    }
                    //	subtract a df
                    ctd--;
                    //	 change sum-squared error into cov.
                    cov[0,0] /= ctd;
                    cov[0,1] /= ctd;
                    cov[0,2] /= ctd;
                    cov[0,3] /= ctd;
                    cov[0,4] /= ctd;

                    cov[1,1] /= ctd;
                    cov[1,2] /= ctd;
                    cov[1,3] /= ctd;
                    cov[1,4] /= ctd;

                    cov[2,2] /= ctd;
                    cov[2,3] /= ctd;
                    cov[2,4] /= ctd;

                    cov[3,3] /= ctd;
                    cov[3,4] /= ctd;

                    cov[4,4] /= ctd;

                    cov[1,0] = cov[0,1];
                    cov[2,0] = cov[0,2];
                    cov[3,0] = cov[0,3];
                    cov[4,0] = cov[0,4];

                    cov[2,1] = cov[1,2];
                    cov[3,1] = cov[1,3];
                    cov[4,1] = cov[1,4];

                    cov[3,2] = cov[2,3];
                    cov[4,2] = cov[2,4];

                    cov[4,3] = cov[3,4];

                    return ct;
                }
                else
                    return 0;
            }
            else
                return 0;
        }
        public int Area
        {
            get
            {
                int output = 0;
                for(int i=0;i<Polygons.Count;i++)
                    output += Polygons[i].Pix.NumPts;
                return output;
            }
        }
        public float PixPerOb
        {
            get
            {
                return (float)Math.Sqrt((double)this.Area);
            }
        }
        public string MediumName
        {
            get
            {
                return this.Category.Owner.ShortProjname +"/"+ this.Category.Name +"/"+ this.Name;
            }
        }
        public string OutputText
        {
            get
            {
                return CatData.OutputText+this.SearchText;
            }
        }
        public string OutputTextShort
        {
            get
            {
                Rectangle r = this.BoundingRect;
                string output = "";
                for(int i=0;i<this.Polygons.Count;i++)
                    output=output + '\t' + ((PolygonF)this.Polygons[i]).DbCache_MaskFileName;
                return this.DbCache_MaskFileName
					+ '\t' + this.Category.Owner.ShortProjname
					+ '\t' + r.Left.ToString() + '\t' + r.Top.ToString() + '\t' +r.Width.ToString() + '\t'+r.Height.ToString() +'\t'
					+ '\t' + this.Category.Name +'\t'+ this.Name + '\t' + CatData.OutputTextShort + '\t'
					+ this.Polygons.Count + output;
            }
        }
        public static string OutputTextFieldNames
        {
            get
            {
                return "MaskName\tImageName\tLeft\tTop\tWidth\tHeight\tCategory\t"
					+ObjectCategorizationData.OutputTextFieldNames
					+'\t' + "NumPoly\tTabSeparatedPolygonMaskNames...";
            }
        }
        public Bitmap GetBitmap32(Bitmap master)
        {
            Rectangle rect = this.BoundingRect;
            Bitmap bmp = new Bitmap(rect.Width,rect.Height,PixelFormat.Format32bppArgb);
            int i,j;
            for(i=0;i<rect.Width;i++)
                for(j=0;j<rect.Height;j++)
                    if(this.PixelInObject(i+rect.Left,j+rect.Top)>=0)
                        bmp.SetPixel(i,j,master.GetPixel(i+rect.Left,j+rect.Top));
                    else
                        bmp.SetPixel(i,j,Color.White);
            return bmp;
        }
        public Bitmap GetBoundingBitmap32(Bitmap master)
        {
            Rectangle rect = this.BoundingRect;
            Bitmap bmp = new Bitmap(rect.Width,rect.Height,PixelFormat.Format32bppArgb);
            int i,j;
            for(i=0;i<rect.Width;i++)
                for(j=0;j<rect.Height;j++)
                    bmp.SetPixel(i,j,master.GetPixel(i+rect.Left,j+rect.Top));
            return bmp;
        }
        public Bitmap GetBackgroundBitmap32(Bitmap master)
        {
            Rectangle rect = this.BoundingRect;
            Bitmap bmp = new Bitmap(rect.Width,rect.Height,PixelFormat.Format32bppArgb);
            int i,j;
            for(i=0;i<rect.Width;i++)
                for(j=0;j<rect.Height;j++)
                    if(this.PixelInObject(i+rect.Left,j+rect.Top)<0)
                        bmp.SetPixel(i,j,master.GetPixel(i+rect.Left,j+rect.Top));
                    else
                        bmp.SetPixel(i,j,Color.White);
            return bmp;
        }
        public void SavePngMask(string pngMaskFileName)
        {
            Rectangle rect = this.BoundingRect;
            if(rect.Width>0 && rect.Height>0)
            {
                Bitmap bmp = new Bitmap(rect.Width,rect.Height,PixelFormat.Format24bppRgb);
                int i,j;
                for(i=0;i<rect.Width;i++)
                    for(j=0;j<rect.Height;j++)
                    {
                        if(PixelInObject(i+rect.Left,j+rect.Top)>=0)
                            bmp.SetPixel(i,j,Color.White);
                        else
                            bmp.SetPixel(i,j,Color.Black);
                    }
                bmp.Save(pngMaskFileName,ImageFormat.Png);
            }
        }
        public void ResetPixelTagger()
        {
            for(int i=0;i<Polygons.Count;i++)
                Polygons[i].ResetPixelTagger();
        }
        public uint DbCache_UniqueId=0u;
        public string DbCache_MaskFileName="";
        public void AssignUniqueIdPoly(ref uint nextPolyId)
        {
            for(int i=0;i<Polygons.Count;i++)
            {
                Polygons[i].DbCache_UniqueId = nextPolyId++;
                Polygons[i].DbCache_MaskFileName = "poly"+Util.PaddedIntegerString((uint)Polygons[i].DbCache_UniqueId,10u)+".png";
            }
        }
        /// <summary>
        /// This assumes that AssignUniqueIdPoly has already been called.
        /// </summary>
        /// <param name="pathname">The path name of the masks.  This algorithm creates a subdirectory for Polygons.</param>
        public void SaveMasksPoly(string pathname)
        {
            for(int i=0;i<this.Polygons.Count;i++)
                Polygons[i].Pix.SavePngMask(pathname + "\\"+Polygons[i].DbCache_MaskFileName);
        }
        public void FindUnstandardizedPolygons(PolygonFarray output)
        {
            for(int i=0;i<this.Polygons.Count;i++)
                if(!this.Polygons[i].OrderIsStandardized)
                    output.Add(this.Polygons[i]);
        }
        /// <summary>
        /// Returns the closest VertexF to a reference point.  The VertexF cannot be in the polygon
        /// named "notInHere".
        /// </summary>
        /// <param name="imgPt">The reference point (in Img coordinates).</param>
        /// <param name="notInHere">This PolygonF will not be searched for a VertexF result.  This
        /// argument can be null.</param>
        /// <param name="pfResult">The polygon that owns the VertexF result.</param>
        /// <param name="vfResult">The result.</param>
        public void ClosestVertexF(
            PointF imgPt,PolygonF notInHere,out PolygonF pfResult,out VertexF vfResult)
        {
            pfResult=null;
            vfResult=null;

            float sqDist=0.0f,dx,dy;

            VertexF vf;

            int i,j;
            PolygonF pf;
            for(i=0;i<Polygons.Count;i++)
            {
                pf = Polygons[i];
                if(pf!=PolygonF.NullPoly)
                {
                    if(pf!=notInHere)
                    {
                        for(j=0;j<pf.Count;j++)
                        {
                            if(pfResult==null)
                            {
                                pfResult = pf;
                                vfResult = pf[j];
                                dx = vfResult.X-imgPt.X;
                                dy = vfResult.Y-imgPt.Y;
                                sqDist = dx*dx+dy*dy;
                            }
                            else
                            {
                                vf = pf[j];
                                dx = vf.X-imgPt.X;
                                dy = vf.Y-imgPt.Y;
                                dx = dx*dx+dy*dy;
                                if(dx<sqDist)
                                {
                                    pfResult = pf;
                                    vfResult = pf[j];
                                    sqDist=dx;
                                }
                            }
                        }
                    }
                }
            }
        }
        public OcclusionBridgeList ValidateBridges()
        {
            bool valid;
            OcclusionBridgeList output = new OcclusionBridgeList();
            OcclusionBridge bridge;
            for(int i=0;i<this.Bridges.Count;i++)
            {
                bridge = Bridges[i];
                valid =
                    bridge.Pf1.Count>2 && bridge.Pf2.Count>2 &&
					((this.Polygons.Contains(bridge.Pf1) && bridge.Pf1.Contains(bridge.Vf1))
					||
					(this.Polygons.Contains(bridge.Pf2) && bridge.Pf2.Contains(bridge.Vf2)));
                if(!valid)
                {
                    output.Add(bridge);
                    Bridges.RemoveAt(i--);
                }
            }
            return output;
        }
        private static PolygonF _pfTemp;
        public void ToMatlabStruct(string varName,EngMATLib.EngMATAccess mat)
        {
            int i,j,k;
            string ss;
            SurfaceBound sb;
            PointF[] pts;
            double[,] spine;
            Edge ed;
            OcclusionBridge br;
            float carry = 0.0f;

            //	Name
            mat.EvaluateAsString(varName + "Name='" + this.Name + "';");
            //	Cat Data
            this.CatData.ToMatlabStruct(varName + "Cat.",mat);
            //	Search Text
            mat.EvaluateAsString(varName + "SearchText='" + this.SearchText + "';");
            //	SurfaceBoundList
            SurfaceBoundList sbl = new SurfaceBoundList(this,2.0f);
            if(sbl.IsValid)
                mat.EvaluateAsString(varName + "SurfacesAreValid=1;");
            else
                mat.EvaluateAsString(varName + "SurfacesAreValid=0;");
            for(i=0;i<sbl.Count;i++)
            {
                ss = varName + "Surfaces(" + (i+1).ToString() + ").";
                mat.EvaluateAsString(ss + "Elements = {};");
                sb = sbl[i];
                for(j=0;j<sb.Elements.Length;j++)
                {
                    if(sb.Elements[j].GetType()==typeof(Edge))
                    {
                        mat.EvaluateAsString(ss + "Elements{" + (j+1).ToString() + ",1}='E';");
                        ed =(Edge)sb.Elements[j];
                        carry=0.0f;
                        pts = ed.EdgePoints(1.0f,ref carry);
                        spine = new double[pts.Length,2];
                        if(sb.ElementGoesForward[j])
                            for(k=0;k<pts.Length;k++)
                            {
                                spine[k,0] = (double)pts[k].X;
                                spine[k,1] = (double)pts[k].Y;
                            }
                        else
                            for(k=0;k<pts.Length;k++)
                            {
                                spine[pts.Length-k-1,0] = (double)pts[k].X;
                                spine[pts.Length-k-1,1] = (double)pts[k].Y;
                            }
                        mat.SetMatrix("temp",spine);
                        mat.EvaluateAsString(ss + "Elements{" + (j+1).ToString() + ",2}=temp;");
                    }
                    else if(sb.Elements[j].GetType()==typeof(OcclusionBridge))
                    {
                        mat.EvaluateAsString(ss + "Elements{" + (j+1).ToString() + ",1}='B';");
                        br = (OcclusionBridge)sb.Elements[j];
                        spine = new double[2,2];
                        if(sb.ElementGoesForward[j])
                        {
                            spine[0,0] = br.Vf1.X;
                            spine[0,1] = br.Vf1.Y;
                            spine[1,0] = br.Vf2.X;
                            spine[1,1] = br.Vf2.Y;
                        }
                        else
                        {
                            spine[0,0] = br.Vf2.X;
                            spine[0,1] = br.Vf2.Y;
                            spine[1,0] = br.Vf1.X;
                            spine[1,1] = br.Vf1.Y;
                        }
                        mat.SetMatrix("temp",spine);
                        mat.EvaluateAsString(ss + "Elements{" + (j+1).ToString() + ",2}=temp;");
                    }
                    else
                        throw new ApplicationException("duh");
                }
            }
            //	Polygons
            for(i=0;i<this.Polygons.Count;i++)
                Polygons[i].ToMatlabStruct(varName + "Polygons.",mat);
        }
        #region Patch Pairs Analysis
        /// <summary>
        /// Finds pairs of circles within the object boundary.
        /// </summary>
        /// <param name="output">A list of Circle[2] pairs is appended.</param>
        /// <param name="nTry">The number of pairs to try and obtain.</param>
        /// <param name="diameter">The diameter of both circles.</param>
        /// <param name="separation">The separation between the edges of the circles.</param>
        public void FindPairsWithin(ArrayList output, int nTry, int diameter, int separation)
        {
            if(this.Polygons.Count==1)
            {
                this.Polygons[0].FindPairsWithin(output,nTry,diameter,separation);
            }
            else if(this.Polygons.Count>1)
            {
                PixelTagger pix = new PixelTagger();
                int i;
                for(i=0; i<this.Polygons.Count; i++)
                    pix.AddPix(this.Polygons[i].Pix);
                pix.ResizeAndMaintain(this.BoundingRect);

                Random rand = ObjectParserData.Rand;

                //  The circles
                Circle acir,bcir=null;
                float sep = (float)separation + (float)diameter;
                double or;
                Point a,b;
                for(i=0;i<nTry;i++)
                {
                    a = pix.RandomPointInPix;
                    or = (rand.NextDouble()-0.5)*Util.TwoPi;
                    b = new Point((int)(0.5f+(float)a.X+sep*Math.Cos(or)),(int)(0.5f+(float)a.Y+sep*Math.Sin(or)));
                    if(pix.PixelIsTagged(b.X,b.Y))
                    {
                        acir = new Circle(new PointF(a.X,a.Y),(float)diameter/2.0f,false);
                        bcir = new Circle(new PointF(b.X,b.Y),(float)diameter/2.0f,false);
                        if(pix.FullyContained(acir) && pix.FullyContained(bcir))
                            output.Add(new Circle[] { acir,bcir });
                    }
                }
            }
        }
        /// <summary>
        /// Finds pairs of circles across the object boundary.
        /// </summary>
        /// <param name="output">A list of Circle[2] pairs is appended as {inner, outer}.</param>
        /// <param name="nTry">The number of pairs to try and obtain.</param>
        /// <param name="radius">The diameter of both circles.</param>
        /// <param name="separation">The separation between the edges of the circles.</param>
        public void FindPairsAcross(ArrayList output, int nTry, int diameter, int separation)
        {
            if(this.Polygons.Count==1)
            {
                this.Polygons[0].FindPairsAcross(output,nTry,diameter,separation);
            }
            else if(this.Polygons.Count>1)
            {
                double[] area = new double[this.Polygons.Count];
                double atot = 0.0;
                int i;
                PolygonF pf;
                for(i=0;i<this.Polygons.Count;i++)
                {
                    pf = this.Polygons[i];
                    area[i] = (double)pf.Pix.NumPts;
                    atot += area[i];
                }
                for(i=0;i<area.Length;i++)
                {
                    this.Polygons[i].FindPairsAcross(output,(int)(0.5+(double)nTry*area[i]/atot),diameter,separation);
                }
                PixelTagger pix = new PixelTagger();
                for(i=0;i<this.Polygons.Count;i++)
                    pix.AddPix(this.Polygons[i].Pix);
                pix.ResizeAndMaintain(this.BoundingRect);
                for(i=0; i<output.Count; i++)
                {
                    if( !pix.FullyExcluded(((Circle[])output[i])[1]) )
                        output.RemoveAt(i--);
                }
            }
        }
        #endregion
    }
}